#!/usr/bin/env python
# -*- mode: python; coding: utf-8; -*-
##--------------------------------------------------------------------------##
##
## Copyright (C) 1998-2003 Markus Oberhumer <markus@oberhumer.com>
## Copyright (C) 2003 Mt. Hood Playing Card Co.
## Copyright (C) 2005-2010 Skomoroh <skomoroh@users.sourceforge.net>
## Copyright (C) 2010-2013 Jeremy Austin-Bardo <tjaustinbardo@gmail.com>
##
## This program is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program.  If not, see <http://www.gnu.org/licenses/>.
##
##-- G23 -- S25 ------------------------------------------------------------##
__all__ = []

from .. import autostacks
from .. import comparecards as cmp_cards
from .. import helpstrings as hs
from .. import pilelib as stack
from .. import stackfill as fill
from ..formlib import Layout
from ..gamelib import info as gi, game, state
from ..hint import DefaultHint
from ..packlib import info as cs, place
from ..piles import pairing
from ..settings import CONF_OPT
from ..utillib.misc import kwdefault
from . import golf

FOURTEEN, FIFTEEN, TWENTYFIVE = 13, 14, 24

GT, SL = gi.GT, gi.SL
StackWrapper = stack.StackWrapper


class Pyramid_Hint(DefaultHint):

    def step010(self, dropstacks, rowstacks):
        """
        step010(dropstacks:tup, rowstacks:tup) -> None

        1) check Tableau piles
          1a) Drop cards
          1b) Move cards

        :param tuple dropstacks: stacks that can drop cards
        :param tuple rowstacks: stacks that can build cards
        """
        super(Pyramid_Hint, self).step010(self, dropstacks,
                                    rowstacks + (self._stacks['set']['talon'],))


class Apophis_Hint(Pyramid_Hint):

    def compute_hints(self):
        """
        compute_hints() -> None

        compute hints - main hint intelligence
        """
        super(Apophis_Hint, self).compute_hints(self)
        # 6) move a card from a ReserveStack to a ReserveStack
        if not self._hints or self._level == 0:
            self.step060(self._stacks['grp']['reservestacks'])

    def step060(self, reservestacks):
        """
        step060(rowstacks:tup, rowstacks:tup, ) -> None

        6) Move ReserveStack to ReserveStack. (10000 .. 19999)

        Given `reservestacks`, for each `reservestacks` TOPCARD score and
        atomic hint for each that builds on `reservestacks`.

        :param tuple reservestacks: stacks that can play cards
        """
        if not reservestacks:
            return
        for from_stack in reservestacks:
            pile = from_stack.cards[-1:]
            if pile:
                for to_stack in (ts for ts in reservestacks
                                   if ts is not from_stack and
                                      ts.cardsBuild(from_stack, pile)):
                    self.add_hint(50000 + len(to_stack.cards) +
                                 len(from_stack.cards), 1, from_stack,
                                 to_stack, None)


class Hurricane_Hint(DefaultHint):

    def step010(self, dropstacks, rowstacks):
        """
        step010(dropstacks:tup, rowstacks:tup) -> None

        1) check Tableau piles
          1a) Drop cards
          1b) Move cards

        :param tuple dropstacks: stacks that can drop cards
        :param tuple rowstacks: stacks that can build cards
        """
        super(Hurricane_Hint, self).step010(dropstacks,
                                    rowstacks + self._stacks['set']['reserves'])


class Baroness_Talon(stack.DealRowTalon):
    """
    Deal 1 card on _row_. Last 7 cards deal on _row_ and _reserve_.
    """

    @property
    def _piles(self):
        """
        _piles -> tup

        :return: deal _rows_ with last deal _rows_ + _reserve_
        :rtype: tuple
        """
        rows = self.game.s['rows']
        if len(self.cards) == 7:
            rows += self.game.s['reserves']
        return rows


class DropWasteTalon(stack.FaceUpWasteTalon):
    """
    Build none. Play top. Deal 1 card on _waste_.
    """

    def releaseHandler(self, event, drag, sound=True):
        """
        releaseHandler(event:obj, drag:obj[, sound:bool=True]) -> None

        :param object drag: current drag pile
        :param object stack: recieving pile.
        :param boolean sound: sound fx?
        """
        if CONF_OPT['mouse_type'] == 'point-n-click':
            drag['stack'].dragMove(drag, self, sound=sound)
            return
        super(DropWasteTalon, self).releaseHandler(event, drag, sound)

    def can_deal_cards(self):
        """
        can_deal_cards() -> bool

        Check parent can_deal_cards() and Game.is_complete.

        :return: deal more cards?
        :rtype: True | False
        """
        return (super(DropWasteTalon, self).can_deal_cards() and
                not self.game.is_complete)


class SumDropKingDropWasteTalon(pairing.SumDropKingMethod, DropWasteTalon):
    """
    Deal 1 card on _waste_. Build none. Play top. Drop pair of from_stack and
    to_stack whose sum is KING or from_stack of KING on _foundation_. Base
    none.
    """
    pass


class PairRankOrSeqDropWasteTalon(pairing.PairRankOrSeqMethod,
                                  DropWasteTalon):
    """
    Deal 1 card on _waste_. Build none. Play top. Drop pair of from_stack and
    to_stack which build either in rank alone or are same rank on
    _foundation_. Base none.
    """
    pass


class SumDropKingWaste(pairing.SumDropKingMethod, stack.WasteStack):
    """
    Build none. Play top. Drop pair of from_stack and to_stack whose sum is
    KING or from_stack of KING on _foundation_. Base none.
    """
    pass


class PairRankOrSeqWaste(pairing.PairRankOrSeqMethod, stack.WasteStack):
    """
    Build none. Play top. Drop pair of from_stack and to_stack which build
    either in rank alone or are same rank on _foundation_. Base none.
    """
    pass


class Pyramid_Foundation(stack.SingleFoundation):
    """
    Build any KING. Play none.
    """

    def cardsBuild(self, from_stack, cards):
        """
        cardsBuild(from_stack:obj, cards:seq) -> bool

        Cards build according parent cardsBuild(), and card is KING.

        :param object from_stack: sending pile
        :param sequence cards: cards from sending pile
        :return: pile accepts cards?
        :rtype: boolean

        note::
          Other cards will be dropped as pairs by from_stack._dropPairMove().
        """
        if not super(Pyramid_Foundation, self).cardsBuild(from_stack, cards):
            return False
        return cmp_cards.isSameRank(cards, rank=cs.KING)


class DropRowStack(stack.BasicRowStack):
    """
    Build none. Play top. Block pile according to other piles with cards.
    """
    _BOTTOM_IMAGE_ = None

    def __init__(self, x, y, game, **cap):
        """
        __init__(x:int, y:int, game:obj[, offset:tup=(0, 3), *capability*])
          -> None

        :ivar blockmap: initilize list of blocking piles.
        :param integer x: left x-coordinate.
        :param integer y: top y-coordinate.
        :param object game: current Game.
        :param integer suit: pile's suit and base_suit
        :keyword tuple offset: offset from left and offset from top

        seealso::
          Module pysollib.stackcaps with details of *capability* keywords.
        """
        cap.setdefault('offset', (0, 3))
        super(DropRowStack, self).__init__(x, y, game, **cap)
        self.blockmap = []

    def cardsBlock(self):
        """
        cardsBlock() -> bool

        Check if pile is blocked by seeing if piles of id have cards.

        :return: pile is blocked?
        :rtype: boolean
        """
        return any([self.game.s['rows'][i].cards for i in self.blockmap])

    def copyModel(self, clone):
        """
        copyModel(clone:obj) -> None

        Update clone to include blockmap with parent copyModel().

        :param object clone: empty clone stack to fill.

        seealso::
          pysollib.hint.AClonedStack
        """
        super(DropRowStack, self).copyModel(clone)
        clone.blockmap = self.blockmap


class SumDropKing_Rowstack(pairing.SumDropKingMethod, DropRowStack):
    """
    Build none. Play top. Drop pair of from_stack and to_stack whose sum is
    KING or from_stack of KING on _foundation_. Base none. Block pile
    according to other neighbor piles with cards.
    """
    pass


class PairRankOrSeq_Rowstack(pairing.PairRankOrSeqMethod, DropRowStack):
    """
    Build none. Play top. Drop pair of from_stack and to_stack which build
    either in rank alone or are same rank on _foundation_. Base none. Block
    pile according to other neighbor piles with cards.
    """
    pass


class Elevator_RowStack(stack.SingleAsDoubleClickMethod, DropRowStack):
    """
    Build none. Play none. Drop top on _waste_.
    """
    pass


class Fifteens_RowStack(pairing.SumJack_RowStack):
    """
    Build none. Play top. Drop pair of from_stack and to_stack whose sum is
    FIFTEEN or seq of JACK QUEEN KING on _foundation_. Base none.
    """

    def _sequence(self, cards):
        """
        _sequence(cards:seq) -> bool

        Checks sequence rank sum is 15. and cards < TEN.

        :param sequence cards: cards from sending pile
        :return: pile accepts cards?
        :rtype: boolean
        """
        return (all(c['rank'] < cs.TEN for c in cards) and
                cmp_cards.sumOf(cards, FIFTEEN))

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return '{0} {1}'.format(hs.PAIR_SUM_FIFTEEN, hs.CARDS_LESS_TEN)


class Hurricane_RowStack(pairing.SumFourteenMethod, stack.BasicRowStack):
    """
    Build none. Play top. Drop pair of from_stack and to_stack whose sum is
    FOURTEEN on _foundation_. Base none.
    """
    pass


class SumKingDropReserve(pairing.SumDropKingMethod, stack.OpenStack):
    """
    Build none. Play top. Drop pair of from_stack and to_stack whose sum is
    KING or from_stack of KING on _foundation_. Base none.
    """
    pass


class Elevens_Reserve(stack.ReserveStack):
    """
    Build 1 card with any JACK QUEEN KING for each. Play top. Drop _reserve_
    piles when filled on _foundation_.
    """

    def cardsBuild(self, from_stack, cards):
        """
        cardsBuild(from_stack:obj, cards:seq) -> bool

        Checks parent.cardsBuild(), then if card is JACK, QUEEN, KING without
        same rank on another pile.

        :param object from_stack: sending pile
        :param sequence cards: cards from sending pile
        :return: pile accepts cards?
        :rtype: boolean
        """
        if not super(Elevens_Reserve, self).cardsBuild(from_stack, cards):
            return False
        return all((not cmp_cards.isSameRank(s.cards[-1:] + cards)
                    and s.TOPCARD['rank'] in (cs.JACK, cs.QUEEN, cs.KING,))
                    for s in self.game.s['reserves'])

    @state.move_state('fill')
    def _auto_fill(self):
        """
        Perfom actions after a pile_move() drop all _reserve_ when each
        filled on _foundation_.
        """
        reserves = self.game.s['reserves']
        if sum(1 for s in reserves if s.cards) == len(reserves):
            if not self.game.can_play_sound:
                self.game._play_sound('droppair', priority=200)
            for pile in reserves:
                pile.move_pile(1, self.s['foundations'][0], frames=4)

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return hs.ONE_J_Q_K


class SuitElevens_Reserve(Elevens_Reserve):
    """
    Build 1 card with any JACK QUEEN KING by same suit for each. Play top.
    Drop _reserve_ piles when filled on _foundation_.
    """

    def cardsBuild(self, from_stack, cards):
        """
        cardsBuild(from_stack:obj, cards:seq) -> bool

        Checks parent.cardsBuild(), then card values are same suit for group.

        :param object from_stack: sending pile
        :param sequence cards: cards from sending pile
        :return: pile accepts cards?
        :rtype: boolean
        """
        if not self(SuitElevens_Reserve, self).cardsBuild(from_stack, cards):
            return False
        return all([cmp_cards.isSameSuit(self.cards[-1:] + cards)
                     for s in self.game.s['reserves']])

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return hs.ONE_J_Q_K_SUIT


class Fifteens_Reserve(stack.ReserveStack):
    """
    Build 1 card with any TEN JACK QUEEN KING for each or cards whose total
    sum is 15. Play top. Drop _reserve_ piles when filled on _foundation_.
    """

    @state.move_state('fill')
    def _auto_fill(self):
        """
        Perfom actions after a move_pile() drop all _reserve_ when pile sum
        correct on _foundation_.
        """
        cards = self
        if ((len(cards) == 4 and cmp_cards.isSumOf(cards, 45))  # 10-J-Q-K
            or cmp_cards.isSumOf(cards, FIFTEEN)):
            if self.game.can_play_sound:
                self.game._play_sound('droppair', priority=200)
            self.move_pile(len(cards), self.s['foundations'][0], frames=4)

    @state.not_state('is_preview')
    def _update_text(self):
        """
        _update_text() -> None

        Override parent _update_text() to indicate sum of ranks in pile, while
        no cards are TEN or greater.
        """
        if not self.texts['misc']:
            return
        if self.cards:
            if any(c['rank'] in (cs.TEN, cs.JACK, cs.QUEEN, cs.KING) for c in self.cards):
                return
            else:
                self.text['misc'].config(text=str(sum(c['rank'] + 1
                                            for c in self.cards)))

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return '{0} {1}'.format(hs.PAIR_SUM_FIFTEEN, hs.SEQUENCE_T_J_Q_K)


class Hurricane_Reserve(pairing.SumFourteenMethod, stack.OpenStack):
    """
    Build none. Play top. Drop pair of from_stack and to_stack whose sum is
    FOURTEEN on _foundation_. Base none.
    """
    pass


class TripleAlliance_Reserve(Elevens_Reserve):
    """
    Build 1 card for each in rank alone. Play top. Drop _reserve_ piles when
    filled on _foundation_.
    """

    def cardsBuild(self, from_stack, cards):
        """
        cardsBuild(from_stack:obj, cards:seq) -> bool

        Checks parent.cardsBuild(), then check sequence of sorted top cards
        from each pile and cards.

        :param object from_stack: sending pile
        :param sequence cards: cards from sending pile
        :return: pile accepts cards?
        :rtype: boolean
        """
        if not super(TripleAlliance_Reserve, self).cardsBuild(from_stack,
                                                              cards):
            return False
        reserve = [r.TOPCARD for r in self.game.s['reserves'] if r.cards]
        if not reserve:
            return True
        reserve += cards
        reserve.sort()
        if len(reserve) in (2, 3):
            return cmp_cards.isRankSequence(reserve, **self.cap)
        return False

    @property
    def _helpString(self):
        """
        _helpString -> str

        :return: helpful string about pile for its canvas label.
        :rtype:  base_string
        """
        return hs.PILE_SEQUENCE_RANK


def setPyramid_iter(l, sx, sy, size, yfactor=2, invert=False):
    """
    setPyramid_iter(l:obj, dx:int, dy:int, size:int[, yfactor:int=2,
      invert:boolt=False]) >> int, int

    Iterate coordinates for a pyramid-shape set piles of size height with
    the smallest on top that overlap by yfactor.

    :param object l: Layout object.
    :param integer dx: start x-coordinate.
    :param integer dy: start y-coordinate.
    :param integer size: number of piles high.
    :keyword int yfactor: reducing factor for YS (YS / yfactor).
    :keyword boolean invert: Create pyramid with largest first.

    note::
      Function was seperated from Pyramid class.

      Simplified to just iterate coordinates for either pyramid type.
    """
    sizer = zip(range(size), range(size, -1, -1))
    if invert:
        sizer.reverse()
    n = int(invert)
    for delta in sizer:  # Vert
        x = sx + (delta[1] - n) * l.XS / 2
        y = sy + l.YS + (delta[n] - n) * l.YS / yfactor
        for pile in range(delta[0] + 1):  # Horz
            yield x, y
            x += l.XS


def mapPyramid_iter(size, invert=False):
    """
    mapPyramid_iter(size:int[, invert:boolt=False]) >> int, (int, int)

    Iterate current index and pile indexes which block for a pyramid-shape set
    piles of size height with the smallest on top that overlap.

    :param integer size: number of piles high.
    :keyword boolean invert: Create pyramid block map with largest first.

    note::
      Function was seperated from Pyramid class.

      Simplified to just iterate index and pile indexes which block for either
      pyramid type.
    """
    index = 0
    if not invert:
        for row in range(1, size):  # All but bottom can be blocked
            for pile in range(row):
                yield index, (index + row, index + row + 1,)
                index += 1
    else:
        for row in range(size, 1, - 1):  # All but bottom can be blocked
            for pile in range(row):
                block = (index + row, index + row - 1)
                if pile == 0:  # left
                    block = (block[0],)
                elif pile + 1 == row:  # right
                    block = (block[1],)
                yield index, block
                index += 1


class Pyramid(game.Game):
    """
    Drop cards on _foundation_ within 3 turns

    _row_
      Pile 28 in pyramid 7 high. Init deal 1 card. Pair sum 13. Pair
      or KING drop on _foundation_. Play top. Gap none.

    _talon_
      Pile 1 NW. Deal _waste_ 1 card.

    _waste_
      Pile 1 S of _talon_. Init deal 1 card. Build none. Play top.

    _foundation_
      Pile 1 NE. Drop pair or KING from _row_. Play none.

    note::
      ⟪quickplay⟫ disabled.
    ------
    :cvar Stack _talon_: SumDropKingDropWasteTalon (max_rounds=3)
    :cvar Stack _waste_: SumDropKingWaste
    :cvar Stack _foundation_: Pyramid_Foundation (dir=0)
    :cvar Stack _row_: SumDropKing_Rowstack
    :cvar Hint _hint_: Pyramid_Hint
    """
    __info__ = dict(id=38, name='Pyramid', game_type=(GT.PAIRING |
                    GT.POPULAR), decks=1, redeals=2, version='3.00',
                    skill_level=SL.MOSTLY_LUCK)
    __author__ = 'Markus Oberhumer'
    __version__ = 7.70

    _talon_ = StackWrapper(SumDropKingDropWasteTalon, max_rounds=3)
    _waste_ = StackWrapper(SumDropKingWaste)
    _foundation_ = StackWrapper(Pyramid_Foundation, dir=0)
    _row_ = SumDropKing_Rowstack
    _hint_ = Pyramid_Hint

    def _create_game(self, size=7, reserves=0, yfactor=2, **kw):
        l, s = Layout(self), self.s
        max_rows = max(size + 2, reserves)
        dy = l.YS + 2 * l.YOFFSET if reserves else 0
        self.table_size = (l.XM + max_rows * l.XS,
                           l.YM + size * l.YS / yfactor + dy,)
        dx, dy = l.XM + l.XS, l.YM
        for x, y in setPyramid_iter(l, dx, dy, size=size,
                                  yfactor=yfactor):
            s['rows'] = self._row_(x, y, game=self)
        for i, blockmap in mapPyramid_iter(size=size):
            s['rows'][i].blockmap = blockmap
        x, y = l.XM, l.YM
        s['talon'] = self._talon_(x, y, game=self)
        l.set_rounds_text(s['talon'], 'ne')
        if self._waste_ is not None:
            y += l.YS
            s['wastes'].append(self._waste_(x, y, game=self))
            l.set_ncards_text(s['talon'], 'se')
            l.set_ncards_text(s['wastes'][-1], 'se')
        x, y = self.width - l.XS, l.YM
        s['foundations'].append(self._foundation_(x, y, game=self))
        l.set_ncards_text(s['foundations'][0], 's')
        if reserves:
            x, y = l.XM + (max_rows - reserves) * l.XS / 2, self.height - dy
            for i in range(reserves):
                s['reserves'].append(self._reserve_(x, y, game=self,
                                                 offset=(0, l.YOFFSET)))
                x += l.XS
        l.defaultStackGroups()
        self.sg['openstacks'] += [s['talon']]
        self.sg['dropstacks'] += [s['talon']]
        if s['wastes']:
            self.sg['openstacks'] += s['wastes']
        return l

    def _start(self, **kw):
        self._play_sound('deal')
        self.s['talon'].dealRow(frames=4)
        self.s['talon'].deal_cards()

    def _autopiles(self, event=None):
        """
        Return piles for auto or quick move.

        Overide to disable quickplay.

        .. \_autopiles(self, event=None) -> seq(seq(Pile ...), seq(Pile ...),
             seq(Pile ...))
        """
        return autostacks.flip_drop(super(Pyramid, self)._autopiles(event))


class RelaxedPyramid(Pyramid):
    """
    Clear _row_ of cards within 3 turns.

    _row_
      Pile 28 in pyramid 7 high. Init deal 1 card. Pair sum 13. Pair
      or KING drop on _foundation_. Play top. Gap none.

    _talon_
      Pile 1 NW. Deal _waste_ 1 card.

    _waste_
      Pile 1 S of _talon_. Init deal 1 card. Build none. Play top.

    _foundation_
      Pile 1 NE. Drop pair or KING from _row_. Play none.

    note::
      ⟪quickplay⟫ disabled.
    ------
    :cvar Stack _talon_: SumDropKingDropWasteTalon (max_rounds=3)
    :cvar Stack _waste_: SumDropKingWaste
    :cvar Stack _foundation_: Pyramid_Foundation (dir=0)
    :cvar Stack _row_: SumDropKing_Rowstack
    :cvar Hint _hint_: Pyramid_Hint
    """
    __info__ = dict(id=193, name='Relaxed Pyramid', game_type=(GT.PAIRING
                    | GT.RELAXED), decks=1, redeals=2,
                    skill_level=SL.MOSTLY_LUCK, version='4.41',
                    altnames=('Pyramidʼs Stones',))
    __author__ = 'Markus Oberhumer'
    __version__ = 7.70

    @property
    def is_complete(self):
        return all(not r.cards for r in self.s['rows'])


class Giza(Pyramid):
    """
    Drop cards on _foundation_ within a turn.

    _row_
      Pile 32 in pyramid 8 high. Init deal 1 card. Pair sum 13. Pair
      or KING drop on _foundation_. Play top. Gap none.

    _reserve_
      Pile 8 S of _row_. Init deal 3 open card. Drop and pair as _row_. Play
      top. Gap none.

    _foundation_
      Pile 1 NE. Drop pair or KING from _row_. Play none.

    note::
      ⟪quickplay⟫ disabled.
    ------
    :cvar Stack _talon_: SumDropKingDropWasteTalon (max_rounds=3)
    :cvar Stack _waste_: None
    :cvar Stack _foundation_: Pyramid_Foundation (dir=0)
    :cvar Stack _row_: SumDropKing_Rowstack
    :cvar Stack _reserve_: SumKingDropReserve
    :cvar Hint _hint_: Pyramid_Hint
    """
    __info__ = dict(id=592, name='Giza', game_type=(GT.PAIRING |
                    GT.OPEN), decks=1, skill_level=SL.BALANCED,
                    version='0.93fc')
    __author__ = 'Skomoroh'
    __credits__ = ['Michael Keller']
    __version__ = 7.70

    _talon_ = stack.InitialDealTalon
    _waste_ = None
    _reserve_ = SumKingDropReserve

    def _create_game(self, **kw):
        kw.setdefault('reserves', 8)
        return super(Giza, self)._create_game(**kw)

    def _start(self, **kw):
        for i in range(3):
            self.s['talon'].dealRow(rows=self.s['reserves'], frames=0)
        self._play_sound('deal')
        self.s['talon'].dealRow(frames=4)


class Thirteen(Pyramid):
    """
    Drop cards on _foundation_ within a turn.

    _row_
      Pile 28 in pyramid 7 high. Init deal 1 only last 2 row open card. Pair
      sum 13. Pair or KING drop on _foundation_. Play top. Gap none.

    _talon_
      Pile 1 NW. Deal _waste_ 1 card.

    _waste_
      Pile 1 S of _talon_. Init deal 1 card. Build none. Play either top 2.

    _foundation_
      Pile 1 NE. Drop pair or KING from _row_. Play none.

    note::
      ⟪quickplay⟫ disabled.
    ------
    :cvar Stack _talon_: SumDropKingDropWasteTalon (max_rounds=3)
    :cvar Stack _waste_: None
    :cvar Stack _foundation_: Pyramid_Foundation (dir=0)
    :cvar Stack _row_: SumDropKing_Rowstack
    :cvar Hint _hint_: Pyramid_Hint
    """
    __info__ = dict(id=44, name='Thirteen', game_type=(GT.PAIRING |
                    GT.HIDDEN), decks=1, version='0.70up')
    __author__ = 'Markus Oberhumer'
    __version__ = 7.70

    def _create_game(self, **kw):
        l, s = super(Thirteen, self)._create_game(**kw), self.s
        self.sg['Talons'] = [s['talon']] + s['wastes']
        self.sg['openstacks'] = s['rows'] + self.sg['Talons']
        self.sg['dropstacks'] = s['rows'] + self.sg['Talons']
        return l

    def _start(self, **kw):
        self._play_sound('deal')
        self.s['talon'].dealRow(rows=self.s['rows'][:21], flip=False)
        self.s['talon'].dealRow(rows=self.s['rows'][21:])
        self.s['talon'].deal_cards()


class Thirteens(Pyramid):
    """
    Drop cards on _foundation_ within a turn.

    _row_
      Pile 10 of 2 rows. Init deal 1 card. Pair sum 13. Pair or KING drop on
      _foundation_. Play top. Gap none. Gap deal from _talon_.

    _talon_
      Pile 1 NW. Deal none.

    _foundation_
      Pile 1 SE. Drop pair or KING from _row_. Play none.

    note::
      ⟪quickplay⟫ disabled.
    ------
    :cvar Stack _talon_: stack.Talon
    :cvar Stack _waste_: None
    :cvar Stack _foundation_: Pyramid_Foundation (dir=0)
    :cvar Stack _row_: SumKingDropReserve
    :cvar Hint _hint_: Pyramid_Hint
    """
    __info__ = dict(id=593, name='Thirteens', game_type=GT.PAIRING,
                    decks=1, skill_level=SL.LUCK, version='0.93fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = stack.Talon
    _row_ = SumKingDropReserve

    def _create_game(self, rows=(5,) * 2, **kw):
        l, s = Layout(self), self.s
        self.table_size = (l.XM + max(rows) * l.XS,
                           l.YM + (len(rows) + 2) * l.YS,)
        x, y = l.XM, l.YM
        for row in rows:
            x = l.XM
            for j in range(row):
                s['rows'].append(self._row_(x, y, game=self))
                x += l.XS
            y += l.YS
        x, y = l.XM, self.height - l.YS
        s['talon'] = (x, y, self)
        l.set_ncards_text(s['talon'], 'n')
        x = self.width - l.XS
        s['foundations'].append(self._foundation_(x, y, game=self))
        l.set_ncards_text(s['foundations'][0], 'n')
        l.defaultStackGroups()
        return l

    def _start(self, **kw):
        self._play_sound('deal')
        self.s['talon'].dealRow()

    @state.move_state('fill')
    def _auto_fill(self, stack):
        """
        Fill stack if _row_ when gap from _talon_.

        *Parameters*:
          stack : Stack
            pile just played
        """
        fill.fromTalon(self, stack, self.s['rows'])


class Elevator(game.Game):
    """
    Clear cards from _row_ within a turn.

    _row_
      Pile 28 in pyramid 7 high. Init deal 1 close card on 1st 21 then 1 open
      card for last row. Drop on _foundation_. Play none.

    _talon_
      Pile 1 NW. Deal _waste_ 1 card.

    _waste_
      Pile 1 E _talon_. Init deal 1 card. Build either in rank alone wrap.
      Play none.

    note::
      ⟪highlightpiles⟫, ⟪quickplay⟫, ⟪dropstacks⟫ disabled.
    ------
    :cvar Stack _talon_: Talon
    :cvar Stack _waste_: UD_RK_Waste (strict=True)
    :cvar Stack _row_: RowStack
    :cvar Hint _hint_: Hint
    """
    __info__ = dict(id=40, name='Elevator', game_type=(GT.GOLF | GT.GNOME),
                    decks=1, skill_level=SL.BALANCED, version='3.00',
                    altnames=('Egyptian Solitaire', 'Pyramid Golf',))
    __author__ = 'Markus Oberhumer'
    __version__ = 7.70

    _talon_ = golf.Talon
    _waste_ = StackWrapper(golf.UD_RK_Waste, mod=13)
    _row_ = Elevator_RowStack
    _hint_ = golf.Hint

    def _create_game(self, size=7, yfactor=2, **kw):
        l, s = Layout(self), self.s
        self.table_size = (l.XM + (size + 2) * l.XS,
                           l.YM + size * l.YS / yfactor,)
        dx, dy = l.XM + l.XS, l.YM
        for x, y in setPyramid_iter(l, dx, dy, size=size,
                                  yfactor=yfactor):
            s['rows'] = self._row_(x, y, game=self)
        for i, blockmap in mapPyramid_iter(size=size):
            s['rows'][i].blockmap = blockmap
        x, y = l.XM, l.YM
        s['talon'] = self._talon_(x, y, game=self)
        l.set_ncards_text(s['talon'], 's')
        x += l.XS
        s['wastes'].append(self._waste_(x, y, self))
        l.set_ncards_text(s['wastes'][-1], 's')
        s['foundations'] = s['wastes']
        self.sg['openstacks'] = s['wastes']
        self.sg['Talons'] = [s['talon']]
        self.sg['dropstacks'] = s['rows']

    def _start(self, flip=False, **kw):
        self._play_sound('deal')
        self.s['talon'].dealRow(rows=self.s['rows'][:21], flip=flip)
        self.s['talon'].dealRow(rows=self.s['rows'][21:])
        self.s['talon'].deal_cards()

    @property
    def is_complete(self):
        return all(not r.cards for r in self.s['rows'])

    @property
    def _highlight_piles(self):
        """
        Get stacks to highlight for possible move.

        :return: empty.
        :rtype: tuple
        """
        return ()

    def _autopiles(self, event=None):
        """
        Return piles for auto or quick move.

        Overide to disable quickplay when event, else autodrop, quickplay.

        .. \_autopiles(self, event=None) -> seq(seq(Pile ...), seq(Pile ...),
             seq(Pile ...))
        """
        piles = super(Elevator, self)._autopiles(event)
        if event is None:
            return autostacks.flip(piles)
        else:
            return autostacks.flip_drop(piles)


class Escalator(Elevator):
    """
    Clear cards from _row_ within a turn.

    _row_
      Pile 28 in pyramid 7 high. Init deal 1 card. Drop on _foundation_. Play
      none.

    _talon_
      Pile 1 NW. Deal _waste_ 1 card.

    _waste_
      Pile 1 E _talon_. Init deal 1 card. Build either in rank alone wrap.
      Play none.

    note::
      ⟪highlightpiles⟫, ⟪quickplay⟫, ⟪dropstacks⟫ disabled.
    ------
    :cvar Stack _talon_: Talon
    :cvar Stack _waste_: UD_RK_Waste (strict=True)
    :cvar Stack _row_: RowStack
    :cvar Hint _hint_: Hint
    """
    __info__ = dict(id=281, name='Escalator', game_type=(GT.GOLF | GT.GNOME),
                    decks=1, skill_level=SL.BALANCED, version='0.80fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    def _start(self, **kw):
        kw.setdefault('flip', True)
        super(Escalator, self)._start(**kw)


class Elevens(Pyramid):
    """
    Drop cards on _foundation_ within a turn.

    _row_
      Pile 9 over 3 rows. Init deal 1 card. Pair sum 11. Pair drop on
      _foundation_. Play top. Gap dealfrom _talon_.

    _reserve_
      Pile 3 S. Build sequence across of JACK - KING. Drop seq on
      _foundation_. Play top.

    _talon_
      Pile 1 SE. Deal none.

    _foundation_
      Pile 1 SE. Drop pair from _row_ or seq from _reserve_. Play none.

    note::
      ⟪quickplay⟫ disabled.
    ------
    :cvar Stack _talon_: stack.Talon
    :cvar Stack _waste_: None
    :cvar Stack _foundation_: stack.SingleFoundation
    :cvar Stack _row_: pairing.SumJack_RowStack
    :cvar Stack _reserve_: Elevens_Reserve
    :cvar Hint _hint_: Pyramid_Hint
    """
    __info__ = dict(id=594, name='Elevens', game_type=GT.PAIRING,
                    decks=1, skill_level=SL.LUCK, version='0.93fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = stack.Talon
    _waste_ = None
    _foundation_ = stack.SingleFoundation
    _row_ = pairing.SumJack_RowStack
    _reserve_ = Elevens_Reserve

    def _create_game(self, rows=(3,) * 3, reserves=3, **kw):
        l, s = Layout(self), self.s
        self.table_size = (l.XM + (max(rows) + 2) * l.XS,
                           l.YM + len(rows) * l.YS + 3 * l.HYS,)
        x, y = self.width - l.XS, l.YM
        s['talon'] = self._talon_(x, y, game=self)
        l.set_ncards_text(s['talon'], 's')
        y = self.height - l.YS
        s['foundations'].append(self._foundation_(x, y, game=self))
        l.set_ncards_text(s['foundations'][0], 'n')
        y = l.YM
        for row in rows:  # Vert
            x = l.XM
            for j in range(row):  # Horz
                s['rows'].append(self._row_(x, y, game=self))
                x += l.XS
            y += l.YS
        x, y = l.XM, self.height - l.YS
        for i in range(reserves):  # Horz
            s['reserves'].append(self._reserve_(x, y, game=self,
                                             offset=(l.XOFFSET, 0)))
            x += l.XS
        l.defaultStackGroups()
        return l

    def _start(self, **kw):
        self._play_sound('deal')
        self.s['talon'].dealRow()

    @state.move_state('fill')
    def _auto_fill(self, stack):
        """
        Fill stack if _row_ when gap from _talon_. Drop _reserve_ when all
        full on _foundation_.

        *Parameters*:
          stack : Stack
            pile just played
        """
        fill.fromTalon(self, stack, self.s['rows'])


class ElevensToo(Elevens):
    """
    Drop cards on _foundation_ within a turn.

    _row_
      Pile 9 over 3 rows. Init deal 1 card. Pair sum 11. Pair drop on
      _foundation_. Play top. Gap deal from _talon_ when _reserve_ no cards.

    _reserve_
      Pile 3 S. Build sequence across of JACK - KING. Drop seq on
      _foundation_. Play top.

    _talon_
      Pile 1 SE. Deal none.

    _foundation_
      Pile 1 SE. Drop pair from _row_ or seq from _reserve_. Play none.

    note::
      ⟪quickplay⟫ disabled.
    ------
    :cvar Stack _talon_: stack.Talon
    :cvar Stack _waste_: None
    :cvar Stack _foundation_: stack.SingleFoundation
    :cvar Stack _row_: pairing.SumJack_RowStack
    :cvar Stack _reserve_: Elevens_Reserve
    :cvar Hint _hint_: Pyramid_Hint
    """
    __info__ = dict(id=595, name='Elevens Too', game_type=GT.PAIRING,
                    decks=1, skill_level=SL.LUCK, version='0.93fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    @state.move_state('fill')
    def _auto_fill(self, stack):
        """
        Fill stack if _row_ when gap from _talon_ when _reserve_ gap. Drop
        _reserve_ when all full on _foundation_.

        *Parameters*:
           stack : Stack
             pile just played
        """
        if sum(1 for s in self.s['reserves'] if s.cards) == 0:
            self.s['talon'].dealRow(row=[s for s in self.s['rows'] if not s.cards])


class SuitElevens(Elevens):
    """
    Drop cards on _foundation_ within a turn.

    _row_
      Pile 15 over 5 rows. Init deal 1 card. Pair sum 11 by same suit. Pair
      drop on _foundation_. Play top. Gap deal from _talon_.

    _reserve_
      Pile 3 S. Build sequence across of JACK - KING by same suit. Drop seq on
      _foundation_. Play top.

    _talon_
      Pile 1 SE. Deal none.

    _foundation_
      Pile 1 SE. Drop pair from _row_ or seq from _reserve_. Play none.

    note::
      ⟪quickplay⟫ disabled.
    ------
    :cvar Stack _talon_: stack.Talon
    :cvar Stack _waste_: None
    :cvar Stack _foundation_: stack.SingleFoundation
    :cvar Stack _row_: pairing.SumJackSS_Rowstack
    :cvar Stack _reserve_: SuitElevens_Reserve
    :cvar Hint _hint_: Pyramid_Hint
    """
    __info__ = dict(id=596, name='Suit Elevens', game_type=GT.PAIRING,
                    decks=1, skill_level=SL.LUCK, version='0.93fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _row_ = pairing.SumJackSS_Rowstack
    _reserve_ = SuitElevens_Reserve

    def _create_game(self, **kw):
        kw.setdefault('rows', (3,) * 5)
        return super(SuitElevens, self)._create_game(**kw)


class Fifteens(Elevens):
    """
    Drop cards on _foundation_ within a turn.

    _row_
      Pile 16 over 4 rows. Init deal 1 card. Pair sum 15. Pair drop on
      _foundation_. Cards > 10 not pair. Play top. Gap deal from _talon_.

    _reserve_
      Pile 1 S. Build sequence across of TEN - KING or pair sum 15. Drop seq
      or pair on _foundation_. Play top.

    _talon_
      Pile 1 SE. Deal none.

    _foundation_
      Pile 1 SE. Drop pair from _row_ or seq from _reserve_. Play none.

    note::
      ⟪quickplay⟫ disabled.
    ------
    :cvar Stack _talon_: stack.Talon
    :cvar Stack _waste_: None
    :cvar Stack _foundation_: stack.SingleFoundation
    :cvar Stack _row_: pairing.SumJackSS_Rowstack
    :cvar Stack _reserve_: Fifteens_Reserve (max_cards=MAX)
    :cvar Hint _hint_: None
    """
    __info__ = dict(id=597, name='Fifteens', game_type=GT.PAIRING,
                    decks=1, skill_level=SL.MOSTLY_LUCK,
                    version='0.93fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _row_ = Fifteens_RowStack
    _reserve_ = StackWrapper(Fifteens_Reserve, max_cards=cs.MAX)
    _hint_ = None

    def _create_game(self, **kw):
        kwdefault(kw, rows=(4,) * 4, reserves=1)
        l, s = super(Fifteens, self)._create_game(**kw), self.s
        l.set_misc_text(s['reserves'][0], anchor='n')
        return l

    @state.move_state('fill')
    def _auto_fill(self, stack=None):
        """
        Fill stack if _row_ when gap from _talon_ when _reserve_ gap. Drop
        _reserve_ when sum is 15 or seq [10..K] on _foundation_.

        *Parameters*:
           stack : Stack
             pile just played
        """
        if len(self.s['reserves'][0].cards) == 0:
            self.s['talon'].dealRow(row=[s for s in self.s['rows'] if not s.cards])


class TripleAlliance(game.Game):
    """
    Drop cards on _foundation_ within a turn.

    _row_
      Pile 18 over 5 rows. Init deal [(3,) * 16 + (2,) * 2] open card. Play
      top on _reserve_.

    _reserve_
      Pile 3 N _row_. Build sequence across in rank. Drop seq on _foundation_.
      Play top.

    _foundation_
      Pile 1 NE. Drop seq from _reserve_. Play none.
    ------
    :cvar Stack _talon_: stack.InitialDealTalon
    :cvar Stack _foundation_: stack.SingleFoundation
    :cvar Stack _row_: stack.BasicRowStack (max_accept=0)
    :cvar Stack _reserve_: TripleAlliance_Reserve
    """
    __info__ = dict(id=619, name='Triple Alliance', game_type=GT.PAIRING,
                    decks=1, skill_level=SL.MOSTLY_SKILL,
                    version='0.93fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = stack.InitialDealTalon
    _foundation = stack.SingleFoundation
    _row_ = StackWrapper(stack.BasicRowStack, max_accept=0)
    _reserve_ = TripleAlliance_Reserve

    def _create_game(self, rows=(4,) * 4 + (2,), **kw):
        l, s = Layout(self), self.s
        dx = l.X + 5 * l.XOFFSET
        self.table_size = (l.XM + 5 * dx, l.YM + 5 * l.YS,)
        x, y = l.XM, l.YM
        for i in range(3):
            s['reserves'].append(self._reserve_(x, y, game=self))
            x += l.XS
        x = self.width - l.XS
        s['foundations'].append(self._foundation_(x, y, game=self))
        l.set_ncards_text(s['foundations'][0], 'nw')
        y += l.YS
        for row in rows:  # Vert
            x = l.XM
            for j in range(row):  # Horz
                s['rows'].append(self._row_(x, y, game=self,
                                         offset=(l.XOFFSET, 0)))
                x += dx
            y += l.YS
        x, y = self.width - l.XS, self.height - l.YS
        s['talon'] = self._talon_(x, y, game=self)
        l.defaultStackGroups()
        return l

    def _start(self, **kw):
        for i in range(2):
            self.s['talon'].dealRow(frames=0)
        self._play_sound('deal')
        self.s['talon'].dealRowAvail()


class Pharaohs(Pyramid):
    """
    Drop cards on _foundation_ within a turn.

    _row_
      Pile 52 in pyramid 2, 7, and 6 high. Init deal 1 card. Pair sum 13. Pair
      or KING drop on _foundation_. Play top. Gap none.

    _foundation_
      Pile 1 NE. Drop pair or difference from _row_. Play none.

    note::
      ⟪quickplay⟫ disabled.
    ------
    :cvar Stack _talon_: stack.InitialDealTalon
    :cvar Stack _waste_: None
    :cvar Stack _foundation_: Pyramid_Foundation (dir=0)
    :cvar Stack _row_: SumDropKing_Rowstack
    :cvar Hint _hint_: Pyramid_Hint
    """
    __info__ = dict(id=655, name='Pharaohs', game_type=GT.PAIRING,
                    decks=1, skill_level=SL.BALANCED,
                    version='0.93fc')
    __author__ = 'Skomoroh'
    __credits__ = ['Bryan Stout']
    __version__ = 7.70

    _talon_ = stack.InitialDealTalon
    _waste_ = None

    def _create_game(self, yfactor=3, **kw):
        l, s = Layout(self), self.s
        self.table_size = (l.XM + 9 * l.XS, l.YM + 17 * l.YS / 3,)
        dx, dy = l.XM, l.YM
        for x, y in setPyramid_iter(l, dx, dy, size=2, yfactor=yfactor):
            s['rows'].append(self._row_(x, y, game=self))
        for i, blockmap in mapPyramid_iter(size=2):
            s['rows'].blockmap = blockmap
        dx += 2 * l.XS
        for x, y in setPyramid_iter(l, dx, dy, size=7, yfactor=yfactor):
            s['rows'].append(self._row_(x, y, game=self))
        for i, blockmap in mapPyramid_iter(size=7):
            s['rows'].blockmap = blockmap
        dx += l.XS / 2
        dy += 3 * l.YS
        for x, y in setPyramid_iter(l, dx, dy, size=6, yfactor=yfactor):
            s['rows'].append(self._row_(x, y, game=self))
        for i, blockmap in mapPyramid_iter(size=6):
            s['rows'].blockmap = blockmap
        x, y = l.XM, self.height - l.YS
        s['talon'] = self._talon_(x, y, game=self)
        x, y = self.width - l.XS, l.YM
        s['foundations'].append(self._foundation_(x, y, game=self))
        l.set_ncards_text(s['foundations'][0], 's')
        l.defaultStackGroups()
        return l

    def _start(self, **kw):
        self._play_sound('deal')
        self.s['talon'].dealRow(frames=4)


class Baroness(Pyramid):
    """
    Drop cards on _foundation_ within a turn.

    _row_
      Pile 5 horz. Init deal 1 card. Pair sum 13. Pair or KING drop on
      _foundation_. Play top. Gap none.

    _talon_
      Pile 1 NW. Deal _row_ 1 card. Last deal _row_ and _reserve_ 1 card.

    _reserve_
      Pile 2 SW. Build none. Play top.

    _foundation_
      Pile 1 E. Drop pair or KING from _row_. Play none.

    note::
      ⟪quickplay⟫ disabled.

    seealso::
      <http://en.wikipedia.org/wiki/Baroness_%28solitaire%29>
    ------
    :cvar Stack _talon_: Baroness_Talon (max_rounds=3)
    :cvar Stack _waste_: None
    :cvar Stack _foundation_: Pyramid_Foundation (dir=0)
    :cvar Stack _row_: SumKingDropReserve (base_rank=ANY)
    :cvar Stack _reserve_: SumKingDropReserve
    :cvar Hint _hint_: Pyramid_Hint
    """
    __info__ = dict(id=657, name='Baroness', game_type=GT.PAIRING,
                    decks=1, skill_level=SL.BALANCED,
                    altnames=('Five Piles',), version='0.93fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = Baroness_Talon
    _waste_ = None
    _row_ = StackWrapper(SumKingDropReserve, base_rank=cs.ANY)
    _reserve_ = SumKingDropReserve

    def _create_game(self, **kw):
        l, s = Layout(self), self.s
        self.table_size = (l.XM + 9 * l.XS,
                          l.YM + max(7 * l.HYS, l._get_stack_height(12, l.YS)),)
        x, y = l.XM, l.YM
        s['talon'] = self._talon_(x, y, game=self)
        l.set_ncards_text(s['talon'], 's')
        x += 2 * l.XS
        for i in range(5):  # Horz
            s['rows'].append(self._row_(x, y, game=self, offset=(0, l.YOFFSET)))
            x += l.XS
        x += l.XS
        s['foundations'].append(self._foundation_(x, y, game=self))
        l.set_ncards_text(s['foundations'][0], 's')
        x, y = l.XM, self.height - l.YS
        for i in range(2):  # Vert
            s['reserves'].append(self._reserve_(x, y, game=self))
            y -= l.YS
        l.defaultStackGroups()
        return l

    def _start(self, **kw):
        self._play_sound('deal')
        self.s['talon'].dealRow()


class Apophis(Pharaohs):
    """
    Drop cards on _foundation_ within 3 turns.

    _row_
      Pile 28 in pyramid 7 high. Init deal 1 card. Pair sum 13. Pair
      or KING drop on _foundation_. Play top. Gap none.

    _talon_
      Pile 1 NW. Deal _reserve_ 1 card.

    _reserve_
      Pile 3 E of _talon_. Init deal 1 card. Build none. Play top.

    _foundation_
      Pile 1 NE. Drop pair or KING from _row_. Play none.

    note::
      ⟪quickplay⟫ disabled.
    ------
    :cvar Stack _talon_: stack.DealReserveRedealTalon (max_rounds=3)
    :cvar Stack _waste_: None
    :cvar Stack _foundation_: Pyramid_Foundation (dir=0)
    :cvar Stack _row_: SumDropKing_Rowstack
    :cvar Stack _reserve_: SumDropKingWaste (max_accept=1)
    :cvar Hint _hint_: Apophis_Hint
    """
    __info__ = dict(id=658, name='Apophis', game_type=GT.PAIRING,
                    decks=1, redeals=2, skill_level=SL.MOSTLY_LUCK,
                    version='0.93fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = StackWrapper(stack.DealReserveRedealTalon, max_rounds=3)
    _waste_ = None
    _row_ = SumDropKing_Rowstack
    _reserve_ = StackWrapper(SumDropKingWaste, max_accept=1)
    _hint_ = Apophis_Hint

    def _create_game(self, reserves=3, yfactor=2, size=7, **kw):
        l, s = Layout(self), self.s
        self.table_size = (l.XM + 9 * l.XS, l.YM + 4 * l.YS,)
        dx, dy = l.XM + 3 * l.XS / 2, l.YM
        for x, y in setPyramid_iter(l, dx, dy, size=7, yfactor=yfactor):
            s['rows'] = self._row_(x, y, game=self)
        for i, blockmap in mapPyramid_iter(size=7):
            s['rows'].blockmap = blockmap
        x, y = l.XM, l.YM
        s['talon'] = self._talon_(x, y, game=self)
        l.set_ncards_text(s['talon'], 'se')
        l.set_rounds_text(s['talon'], 'ne')
        y += l.YS
        for i in range(reserves):  # Vert
            s['reserves'].append(self._reserve_(x, y, game=self))
            l.set_ncards_text(s['reserves'][i], 'se')
            y += l.YS
        x, y = self.width - l.XS, l.YM
        s['foundations'].append(self._foundation_(x, y, game=self))
        l.set_ncards_text(s['foundations'][0], 'nw')
        l.defaultStackGroups()
        return l

    def _start(self, **kw):
        self._play_sound('deal')
        self.s['talon'].dealRow(frames=4)
        self.s['talon'].deal_cards()


class Cheops(Pyramid):
    """
    Drop cards on _foundation_ within a turn.

    _row_
      Pile 28 in pyramid 7 high. Init deal 1 card. Pair same suit or differ by
      rank either wrap. Pair or difference drop on _foundation_. Play top. Gap
      none.

    _talon_
      Pile 1 NW. Deal _waste_ 1 card. Pair and drop as _row_ with other piles.
      Play top.

    _waste_
      Pile 1 S of _talon_. Init deal 1 card. Pair and drop as _row_ with other
      piles. Play top.

    _foundation_
      Pile 1 NE. Drop pair or difference from _row_. Play none.

    note::
      ⟪quickplay⟫ disabled.
    ------
    :cvar Stack _talon_: PairRankOrSeqDropWasteTalon (dir=1, mod=13)
    :cvar Stack _waste_: PairRankOrSeqWaste (dir=1, mod=13)
    :cvar Stack _foundation_: stack.SingleFoundation
    :cvar Stack _row_: PairRankOrSeq_Rowstack (dir=1, mod=13)
    :cvar Hint _hint_: Pyramid_Hint
    """
    __info__ = dict(id=659, name='Cheops', game_type=GT.PAIRING,
                    decks=1, skill_level=SL.MOSTLY_SKILL,
                    version='0.93fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = StackWrapper(PairRankOrSeqDropWasteTalon, dir=1, mod=13)
    _waste_ = StackWrapper(PairRankOrSeqWaste, dir=1, mod=13)
    _foundation_ = stack.SingleFoundation
    _row_ = StackWrapper(PairRankOrSeq_Rowstack, dir=1, mod=13)


class Exit(game.Game):
    """
    Drop cards on _foundation_ within a turn.

    _row_
      Pile 10 over 2 row. Init deal 5 card once. Pair sum 11 or 2 JACK or
      QUEEN and KING. Pair drop on _foundation_. Play top.

    _reserve_
      Pile 1 E. Init deal 2 open card. Pair as _row_. Drop as _row_. Play top.

    _foundation_
      Pile 1 SE. Drop pair or JACK or QUEEN and KING from _row_. Play none.

    note:: Any 5 cards with 3 JACK middle card move to pile bottom.
    ------
    :cvar Stack _talon_: stack.InitialDealTalon
    :cvar Stack _foundation_: stack.SingleFoundation
    :cvar Stack _row_: pairing.SumJackorSeqQueenKing_RowStack
    :cvar Stack _reserve_: pairing.SumJackorSeqQueenKing_RowStack
    """
    __info__ = dict(id=674, name='Exit', game_type=GT.PAIRING,
                    decks=1, skill_level=SL.MOSTLY_SKILL,
                    version='1.00fc')
    __author__ = 'Skomoroh'
    __credits__ = ['David Parlett']
    __version__ = 7.70

    _talon_ = stack.InitialDealTalon
    _foundation_ = stack.SingleFoundation
    _row_ = _reserve_ = pairing.SumJackorSeqQueenKing_RowStack

    def _create_game(self, rows=(5,) * 2, **kw):
        l, s = Layout(self), self.s
        dy = l.YS + 5 * l.YOFFSET
        self.table_size = (l.XM + 7 * l.XS, l.YM + len(rows) * dy + l.YS,)
        y = l.YM
        for row in rows:  # Vert
            x = l.XM
            for j in range(row):  # Horz
                s['rows'].append(self._row_(x, y, game=self,
                                         offset=(0, l.YOFFSET)))
                x += l.XS
            y += dy
        x, y = self.width - l.XS, l.YM
        s['reserves'].append(self._reserve_(x, y, game=self,
                                         offset=(0, l.YOFFSET)))
        y = self.height - l.YS
        s['foundations'].append(self._foundation_(x, y, game=self))
        l.set_ncards_text(s['foundations'][0], 'n')
        x = l.XM
        s['talon'] = self._talon_(x, y, game=self)
        l.defaultStackGroups()
        return l

    def _pack_place(self, cards):
        """
        _pack_place(cards:seq(Card ...)) -> seq(Card ...)

        Overide to return JACK so no more than 2 in a _row_.
        """
        return place.two_in(cards, ncards=5, rank=cs.JACK)

    def _start(self, **kw):
        self._play_sound('deal')
        for i in range(10):
            for j in range(5):  # Deal one pile not row at a time
                self.s['talon'].dealRow(rows=[self.s['rows'][i]], frames=4)
        self.s['talon'].dealRow(rows=self.s['reserves'], frames=4)
        self.s['talon'].dealRow(rows=self.s['reserves'], frames=4)


class TwoPyramids(Pyramid):
    """
    Drop cards on _foundation_ within 3 turns.

    _row_
      Pile 56 in 2 pyramid 7 high. Init deal 1 card. Pair sum 13. Pair
      or KING drop on _foundation_. Play top. Gap none.

    _talon_
      Pile 1 NW. Deal _waste_ 1 card.

    _waste_
      Pile 1 S of _talon_. Init deal 1 card. Build none. Play top.

    _foundation_
      Pile 1 NE. Drop pair or KING from _row_. Play none.

    note::
      ⟪quickplay⟫ disabled.
    ------
    :cvar Stack _talon_: SumDropKingDropWasteTalon (max_rounds=3)
    :cvar Stack _waste_: SumDropKingWaste
    :cvar Stack _foundation_: Pyramid_Foundation (dir=0)
    :cvar Stack _row_: SumDropKing_Rowstack
    :cvar Hint _hint_: Pyramid_Hint
    """
    __info__ = dict(id=677, name='Two Pyramids', game_type=(GT.PAIRING
                    | GT.ORIGINAL), decks=2, redeals=2, version='1.00fc',
                    skill_level=SL.MOSTLY_LUCK)
    __author__ = 'Skomoroh'
    __version__ = 7.70

    def _create_game(self, sizes=(7,) * 2, yfactor=2, **kw):
        l, s = Layout(self), self.s
        self.table_size = (l.XM + sum(sizes) * l.XS,
                           l.YM + max(sizes) * l.YS / yfactor,)
        dx, dy = l.XM, l.YM + l.YS
        for size in sizes:
            for x, y in setPyramid_iter(l, dx, dy, size=size,
                                        yfactor=yfactor):
                s['rows'] = self._row_(x, y, game=self)
            for i, blockmap in mapPyramid_iter(size=size):
                s['rows'].blockmap = blockmap
            dx += size * l.XS
        x, y = l.XM, l.YM
        s['talon'] = self._talon_(x, y, game=self)
        l.set_ncards_text(s['talon'], 'se')
        l.set_rounds_text(s['talon'], 'ne')
        y += l.YS
        s['wastes'].append(self._waste_(x, y, game=self))
        l.set_ncards_text(s['wastes'][-1], 'se')
        x, y = self.width - l.XS, l.YM
        s['foundations'].append(self._foundation_(x, y, game=self))
        l.set_ncards_text(s['foundations'][0], 'nw')
        l.defaultStackGroups()
        self.sg['openstacks'] += [s['talon']] + s['wastes']
        self.sg['dropstacks'] += [s['talon']]
        return l


class KingTut(RelaxedPyramid):
    """
    Clear _row_ of cards without limit turns.

     _row_
      Pile 28 in pyramid 7 high. Init deal 1 card. Pair sum 13. Pair
      or KING drop on _foundation_. Play top. Gap none.

    _talon_
      Pile 1 NW. Deal _waste_ 3 card.

    _waste_
      Pile 1 S of _talon_. Init deal 1 card. Build none. Play top.

    _foundation_
      Pile 1 NE. Drop pair or KING from _row_. Play none.

    note::
      ⟪quickplay⟫ disabled.
    ------
    :cvar Stack _talon_: stack.WasteTalon (max_rounds=INFINITE,
      num_deal=3)
    :cvar Stack _waste_: SumDropKingWaste
    :cvar Stack _foundation_: Pyramid_Foundation (dir=0)
    :cvar Stack _row_: SumDropKing_Rowstack
    :cvar Hint _hint_: Pyramid_Hint
    """
    __info__ = dict(id=681, name='King Tut', game_type=GT.PAIRING,
                    decks=1, redeals=cs.INFINITE, skill_level=SL.MOSTLY_LUCK,
                    version='1.00fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = StackWrapper(stack.WasteTalon, max_rounds=cs.INFINITE,
                           num_deal=3)

    def _create_game(self, size=7, wastecards=23, yfactor=2, **kw):
        l, s = Layout(self), self.s
        self.table_size = (l.XM +
                  max(size * l.XS, l.XS + l._get_stack_width(wastecards, l.XS)),
                  l.YM + size * l.YS / yfactor + 3 * l.HYS,)
        dx, dy = l.XM + (self.width - 7 * l.XS) / 2, l.YM
        for x, y in setPyramid_iter(l, dx, dy, size=size, yfactor=yfactor):
            s['rows'] = self._row_(x, y, game=self)
        for i, blockmap in mapPyramid_iter(size=size):
            s['rows'].blockmap = blockmap
        x, y = l.XM, self.height - l.YS
        s['talon'] = self._talon_(x, y, game=self)
        l.set_ncards_text(s['talon'], 'n')
        x += l.XS
        s['wastes'].append(self._waste_(x, y, game=self, offset=(l.XOFFSET, 0)))
        l.set_ncards_text(s['wastes'][-1], 'n')
        x, y = self.width - l.XS, l.YM
        s['foundations'].append(self._foundation_(x, y, game=self))
        l.set_ncards_text(s['foundations'][0], 'nw')
        l.defaultStackGroups()
        self.sg['openstacks'] += s['wastes']
        return l


class DoublePyramid(Pyramid):
    """
    Drop cards on _foundation_ within 3 turns.

    _row_
      Pile 45 in pyramid 9 high. Init deal 1 card. Pair sum 13. Pair
      or KING drop on _foundation_. Play top. Gap none.

    _talon_
      Pile 1 NW. Deal _waste_ 1 card.

    _waste_
      Pile 1 S of _talon_. Init deal 1 card. Build none. Play top.

    _foundation_
      Pile 1 NE. Drop pair or KING from _row_. Play none.

    note::
      ⟪quickplay⟫ disabled.
    ------
    :cvar Stack _talon_: SumDropKingDropWasteTalon (max_rounds=3)
    :cvar Stack _waste_: SumDropKingWaste
    :cvar Stack _foundation_: Pyramid_Foundation (dir=0)
    :cvar Stack _row_: SumDropKing_Rowstack
    :cvar Hint _hint_: Pyramid_Hint
    """
    __info__ = dict(id=699, name='Double Pyramid', game_type=GT.PAIRING,
                    decks=2, redeals=2, skill_level=SL.MOSTLY_LUCK,
                    version='1.00fc', altnames=('Pyramid (2 Decks)',))
    __author__ = 'Skomoroh'
    __version__ = 7.70

    def _create_game(self, **kw):
        kw.setdefault('size', 9)
        return super(DoublePyramid, self)._create_game(**kw)


class Triangle(Pyramid):
    """
    Drop cards on _foundation_ within 3 turns.

    _row_
      Pile 28 in pyramid 7 low. Init deal 1 card. Pair sum 13. Pair or KING
      drop on _foundation_. Play top. Gap none.

    _talon_
      Pile 1 NW. Deal _waste_ 1 card.

    _waste_
      Pile 1 S of _talon_. Init deal 1 card. Build none. Play top.

    _foundation_
      Pile 1 NE. Drop pair or KING from _row_. Play none.

    note::
      ⟪quickplay⟫ disabled.
    ------
    :cvar Stack _talon_: SumDropKingDropWasteTalon (max_rounds=3)
    :cvar Stack _waste_: SumDropKingWaste
    :cvar Stack _foundation_: Pyramid_Foundation (dir=0)
    :cvar Stack _row_: SumDropKing_Rowstack
    :cvar Hint _hint_: Pyramid_Hint
    """
    __info__ = dict(id=700, name='Triangle', game_type=GT.PAIRING,
                    decks=1, redeals=2, skill_level=SL.MOSTLY_LUCK,
                    version='1.00fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    def _create_game(self, size=7, yfactor=2, **kw):
        l, s = Layout(self), self.s
        self.table_size = (l.XM + (size + 3) * l.XS,
                           l.YM + size * l.YS / yfactor,)
        dx, dy = l.XM + 3 * l.XS / 2, l.YM
        for x, y in setPyramid_iter(l, dx, dy, size=size,
                                  yfactor=yfactor, invert=True):
            s['rows'] = self._row_(x, y, game=self)
        for i, blockmap in mapPyramid_iter(size=size, invert=True):
            s['rows'].blockmap = blockmap
        x, y = l.XM, l.YM
        s['talon'] = self._talon_(x, y, game=self)
        l.set_ncards_text(s['talon'], 'se')
        l.set_rounds_text(s['talon'], 'ne')
        y += l.YS
        s['wastes'].append(self._waste_(x, y, game=self))
        l.set_ncards_text(s['wastes'][-1], 'se')
        x, y = self.width - l.XS, l.YM
        s['foundations'].append(self._foundation_(x, y, game=self))
        l.defaultStackGroups()
        self.sg['openstacks'] += [s['talon']] + s['wastes']
        self.sg['dropstacks'] += [s['talon']]
        return l


class UpAndDown(Pyramid):
    """
    Drop cards on _foundation_ within 3 turns.

    _row_
      Pile 56 in pyramid 7 high and 7 low. Init deal 1 card. Pair sum 13. Pair
      or KING drop on _foundation_. Play top. Gap none.

    _talon_
      Pile 1 NW. Deal _waste_ 1 card.

    _waste_
      Pile 1 S of _talon_. Init deal 1 card. Build none. Play top.

    _foundation_
      Pile 1 SE. Drop pair or KING from _row_. Play none.

    note::
      ⟪quickplay⟫ disabled.
    ------
    :cvar Stack _talon_: SumDropKingDropWasteTalon (max_rounds=3)
    :cvar Stack _waste_: SumDropKingWaste
    :cvar Stack _foundation_: Pyramid_Foundation (dir=0)
    :cvar Stack _row_: SumDropKing_Rowstack
    :cvar Hint _hint_: Pyramid_Hint
    """
    __info__ = dict(id=701, name='Up and Down', game_type=(GT.PAIRING
                    | GT.ORIGINAL), decks=2, redeals=2,
                    skill_level=SL.MOSTLY_LUCK, version='1.00fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    def _create_game(self, size=7, yfactor=2, **kw):
        l, s = Layout(self), self.s
        self.table_size = (l.XM + 13 * l.XS, l.YM + 4 * l.YS,)
        dx, dy = l.XM + l.XS / 2, l.YM
        for invert in (False, True,):
            for x, y in setPyramid_iter(l, dx, dy, size=size,
                                        yfactor=yfactor, invert=invert):
                s['rows'] = self._row_(x, y, game=self)
            for i, blockmap in mapPyramid_iter(size=size, invert=invert):
                s['rows'].blockmap = blockmap
            x += 11 * l.XS / 2
        x, y = l.XM, l.YM
        s['talon'] = self._talon_(x, y, game=self)
        l.set_ncards_text(s['talon'], 'se')
        l.set_rounds_text(s['talon'], 'ne')
        y += l.YS
        s['wastes'].append(self._waste_(x, y, game=self))
        l.set_ncards_text(s['wastes'][-1], 'se')
        x, y = self.width - l.XS, self.height - l.YS
        s['foundations'].append(self._foundation_(x, y, game=self))
        l.set_ncards_text(s['foundations'][0], 'sw')
        l.defaultStackGroups()
        self.sg['openstacks'] += [s['talon']] + s['wastes']
        self.sg['dropstacks'] += [s['talon']]
        return l

    def _start(self, **kw):
        self._play_sound('deal')
        self.s['talon'].dealRow(frames=4)
        self.s['talon'].deal_cards()


class Hurricane(game.Game):
    """
    Drop cards on _foundation_ within a turn.

    _row_
      Pile 3 horz. Init deal 1 card. Pair by sum 14. Drop pair on
      _foundation_. Play top. Gap deal from _talon_.

    _reserve_
      Pile 10 W N E S _row. Init deal 3 open card. Pair by sum 14. Drop pair
      on _foundation_. Play top. Gap deal from _talon_.

    _talon_
      Pile 1 NW.

    _foundation_
      Pile 1 W. Drop pair from _row_. Play none.

    note::
      ⟪quickplay⟫ disabled.
    ------
    :cvar Stack _talon_: stack.Talon
    :cvar Stack _foundation_: stack.SingleFoundation (dir=0)
    :cvar Stack _row_: Hurricane_RowStack
    :cvar Stack _reserve_: Hurricane_Reserve
    :cvar Hint _hint_: Hurricane_Hint
    """
    __info__ = dict(id=735, name='Hurricane', game_type=GT.PAIRING,
                    decks=1, skill_level=SL.MOSTLY_LUCK,
                    version='1.10fc')
    __author__ = 'Skomoroh'
    __version__ = 7.70

    _talon_ = stack.Talon
    _foundation_ = StackWrapper(stack.SingleFoundation, dir=0)
    _row_ = Hurricane_RowStack
    _reserve_ = Hurricane_Reserve
    _hint_ = Hurricane_Hint

    def _create_game(self, **kw):
        l, s = Layout(self), self.s
        ddx = l.XS + max(l._get_stack_width(2), l.HXS)
        self.table_size = (l.XM + 3 * l.XS + 4 * ddx, l.YM + 3 * l.YS,)
        x = l.XM + 3 * l.HXS
        for dy, row in zip(range(3), [(0, 1, 2, 3), (0, 3), (0, 1, 2, 3)]):
            y = l.YM + dy * l.YS
            for dx in row:  # Horz
                s['reserves'].append(self._reserve_(x + dx * ddx, y, game=self,
                                                 offset=(l.XOFFSET, 0)))
        x += ddx
        y = l.YM + l.YS
        for i in range(3):  # Horz
            s['rows'].append(self._row_(x, y, game=self))
            x+= l.XS
        x, y = l.XM, l.YM
        s['talon'] = (x, y, self)
        l.set_ncards_text(s['talon'], 'ne')
        y += 2 * l.YS
        s['foundations'].append(self._foundation_(x, y, game=self))
        l.set_ncards_text(s['foundations'][0], 'ne')
        l.defaultStackGroups()
        return l

    def _start(self, **kw):
        for i in range(2):
            self.s['talon'].dealRow(rows=self.s['reserves'], frames=0)
        self._play_sound('deal')
        self.s['talon'].dealRow(rows=self.s['reserves'])
        self.s['talon'].dealRow()

    @state.move_state('fill')
    def _auto_fill(self, stack):
        """
        Fill stack if _row_ when gap from _talon_.

        *Parameters*:
           stack : Stack
             pile just played
        """
        fill.fromTalon(self, stack, self.s['rows'])

    def _autopiles(self, event=None):
        """
        Return piles for auto or quick move.

        Overide to disable autodrop.

        .. \_autopiles(self, event=None) -> seq(seq(Pile ...), seq(Pile ...),
             seq(Pile ...))
        """
        return autostacks.flip_quickplay(super(Hurricane, self)._autopiles(event))

PROVIDES = [Pyramid, RelaxedPyramid, Giza, Thirteens, Elevens, ElevensToo,
            Fifteens, TripleAlliance, Pharaohs, Baroness, Apophis, Cheops,
            Exit, TwoPyramids, KingTut, DoublePyramid, Triangle, UpAndDown,
            SuitElevens, Hurricane, Thirteen, Elevator, Escalator]